using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Search.Query;
using System.Xml.Serialization;
using ASPNET_WebPart = System.Web.UI.WebControls.WebParts;
using System.Data;

namespace Microsoft.SharePoint.Search.Custom
{
    /// <summary>
    /// Web part to execute the actual wildcard search.
    /// Most of what was done is documented in the SDK and MSDN sites. 
    /// Below are several links related to this information.
    /// <para>
    /// <list type="bullet">
    /// <item>http://msdn2.microsoft.com/en-us/library/ms469765.aspx</item>
    /// <item>http://msdn2.microsoft.com/en-us/library/ms551453.aspx</item>
    /// <item>http://msdn.microsoft.com/msdnmag/issues/06/02/BasicInstincts/</item>
    /// <item>http://msdn2.microsoft.com/en-us/library/ms499375.aspx</item>
    /// <item>http://msdn2.microsoft.com/en-us/library/ms546216.aspx</item>
    /// <item>http://msdn2.microsoft.com/en-us/library/bb219479.aspx</item>
    /// <item>http://msdn2.microsoft.com/en-us/library/ms519321.aspx</item>
    /// </list>
    /// </para>
    /// </summary>
    [XmlRoot(Namespace = "MicrosoftCustomSearch")]
    [ToolboxData("<{0}:MicrosoftSearchControl runat=server></{0}:MicrosoftSearchControl>")]
    public class MicrosoftCustomSearchControl : WebPart, IXMLData
    {
        //Inherit from WebPart Class
        //Implement data transfer interface for WebPart connection.

        /// <summary>
        /// Button to execute search
        /// </summary>
        protected Button cmdSearch;

        /// <summary>
        /// Search Textbox
        /// </summary>
        protected TextBox txtQueryText;

        /// <summary>
        /// Control to show messages
        /// </summary>
        //Label lblQueryResult;
        protected LiteralControl lblQueryResult;

#if DEBUG
        /// <summary>
        /// List of datagrids showing in the control
        /// Used primarily when debugging is turned on.
        /// </summary>
        protected List<DataGrid> grdList = new List<DataGrid>();
#endif

        /// <summary>
        /// Dataset to store resulting data from query
        /// </summary>
        protected DataSet resultDS = new DataSet();

        /// <summary>
        /// Deafult columns returned if a 
        /// blank update to the search columns is set.
        /// </summary>
        const string _defaultSearchColumns = "WorkId, rank, RankDetail, title, author, Size, path, Description, Write, "
            + "SiteName, CollapsingStatus, HitHighlightedSummary, HitHighlightedProperties, "
            + "ContentClass, IsDocument, PictureThumbnailURL, PictureURL, "
            + "Firstname, LastName, Account, AccountName, Email, "
            + "AboutMe, Fax, Manager, SipAddress, skills, department, "
            + "WorkAddress, WorkCity, WorkCountry, WorkPhone, WorkState, WorkZip, WorkEmail, "
            + "JobTitle, Responsibilities";

        /// <summary>
        /// String to store the columns returned
        /// </summary>
        /// <remarks>Allows to dynamically control what 
        /// columns are returned in the search.</remarks>
        private string _searchColumns = _defaultSearchColumns;

        ///Property to change the columns returned. The value 
        ///is only stored in the Shared Storage. 
        ///It can't be personalized.
        [Browsable(true), Category("Search Information")]
        [DefaultValue(""), WebPartStorage(Storage.Shared)]
        [FriendlyName("Columns"), Description("Search Columns to return (based on Index Fields). Comma seperated list.")]
        public string SearchColumns
        {
            get
            {
                return this._searchColumns;
            }
            set
            {
                this._searchColumns = value;
                
            }
        }

            /// <summary>
        /// Method to create child controls when needed.
        /// </summary>
        /// <remarks>All the basic controls are created at this level.</remarks>
        protected override void CreateChildControls()
        {
            //Clear out any existing controls
            Controls.Clear();

            //Create search Textbox
            txtQueryText = new TextBox();
            this.Controls.Add(txtQueryText);
            
            //Create Search button
            cmdSearch = new Button();
            cmdSearch.Text = "Start Search";

            //Register event handler for button click.
            cmdSearch.Click += new EventHandler(cmdSearch_Click);

            //Register event handler for button click.
            //AddHandler cmdSearch.Click, AddressOf Me.cmdSearch_Click
            this.Controls.Add(cmdSearch);

            //Create no result label
            lblQueryResult = new LiteralControl();
            this.Controls.Add(lblQueryResult);

        }

        /// <summary>
        /// When search is clicked, handle event and show results
        /// </summary>
        /// <param name="sender">Control Sendind the event</param>
        /// <param name="e">Event parameters</param>
        /// <remarks>This method is fired when the user
        /// clicks the button for search. This method fills a dataset
        /// and allows the results to be returned as XML in the interface.
        /// First the titleNameQueryExecute is fired then 
        /// the fullTextQueryExecute.</remarks>
        protected void cmdSearch_Click(Object sender, EventArgs e)
        {
            if (txtQueryText.Text != string.Empty)
            {
                ///Clear the DataSet to receive new data.
                resultDS.Clear();
                resultDS.Tables.Clear();
                resultDS.DataSetName = "ResultSet";

                this.checkColumns();

                ///Run the search query based on title, 
                ///first name, and last name.
                titleNameQueryExecute(txtQueryText.Text);

                ///Run the search query based on 
                ///all attributes in search
                fullTextQueryExecute(txtQueryText.Text);
                ///Run the search query based on 
                ///all attributes in search
                //freeTextQueryExecute(txtQueryText.Text);

#if DEBUG
                //If in Debug mode, Show a grid of the 
                //relevant results returned
                fillResultsGrid(resultDS);
#endif
            }
            else
            {
                lblQueryResult.Text = "You must enter a search word.";

            }
        }

        /// <summary>
        /// Method to search the index for items that match on 
        /// any field for the query terms.
        /// </summary>
        /// <param name="strQueryText">Search Terms to look for</param>
        /// <remarks>This method uses the FullTextSQLQuery syntax to do a
        /// freetext search on fields.</remarks>
        private void freeTextQueryExecute(string strQueryText)
        {
            //Create the FullTextSqlQuery object for searching the index
            FullTextSqlQuery kRequest = new FullTextSqlQuery(ServerContext.Current);

            //Prepare the string to use wildcards
            //string strQuery = "'\"" + strQueryText.Replace(" ", "*\" AND \"") + "*\"'";
            string strQuery = "'" + strQueryText.Replace("'","''") + "'";


            //Output the search text to the Trace Listeners
            System.Diagnostics.Trace.TraceInformation("The Search text: " + strQuery);

            //Set the query text to return columns and search appropriate fields.
            //The fields are based on what's indexed in the search settings.
            kRequest.QueryText = "select " + this._searchColumns + " FROM Scope() WHERE (\"scope\"='All Sites' "
            + "OR \"scope\"='People') AND FREETEXT(*," + strQuery + ") ORDER BY Rank DESC";

            //Write the query to the Trace listeners
            System.Diagnostics.Trace.TraceInformation("The Query text: " + kRequest.QueryText);
            System.Diagnostics.Trace.Flush();

            //Return all result types except best bets
            kRequest.ResultTypes = ResultType.HighConfidenceResults | ResultType.DefinitionResults | ResultType.RelevantResults;

            //Get the result table collection from the query
            ResultTableCollection resultTbls;
            try
            {
                resultTbls = kRequest.Execute();
            }
            catch (Exception ex)
            {
                ///Write error out to trace listener
                System.Diagnostics.Trace.TraceError("Exception: " + Environment.NewLine + ex.ToString());
                resultTbls = null;
                return;
            }
            finally
            {
                ///Flush trace statements out to listeners
                System.Diagnostics.Trace.Flush();
            }
            //Write the type of tables and number of entries returned to
            //the trace listeners
            System.Diagnostics.Trace.TraceInformation("Result Tables Returned");
            foreach (ResultTable resulttbl in resultTbls)
            {
                System.Diagnostics.Trace.TraceInformation("ResultType: " + resulttbl.ResultType.ToString());
                System.Diagnostics.Trace.TraceInformation("Rows Returned: " + resulttbl.RowCount.ToString() + Environment.NewLine);
            }

            //For each table type write the results to the dataset if they are not empty and exists
            if (((int)ResultType.HighConfidenceResults != 0) && (resultTbls.Exists(ResultType.HighConfidenceResults)))
            {
                ResultTable tblResult = resultTbls[ResultType.HighConfidenceResults];
                if (tblResult.TotalRows == 0)
                {
                    lblQueryResult.Text = "No Search Results Returned.";
                }
                else
                {
                    lblQueryResult.Text = String.Empty;
                    ReadHighResultTable(tblResult);
                }

            }

            if (((int)ResultType.RelevantResults != 0) && (resultTbls.Exists(ResultType.RelevantResults)))
            {
                ResultTable tblResult = resultTbls[ResultType.RelevantResults];
                if (tblResult.TotalRows == 0)
                {
                    lblQueryResult.Text = "No Search Results Returned.";
                }
                else
                {
                    lblQueryResult.Text = string.Empty;
                    ReadResultTable(tblResult);
                }

            }
        }

        /// <summary>
        /// Default ReadResultTable Method modified to use a table name for the dataset
        /// </summary>
        /// <param name="rt">Search Result Table</param>
        /// <remarks>This was modified to use a table name to add to the dataset. 
        /// Makes this method reusable across the webpart.</remarks>
        private void ReadResultTable(ResultTable rt)
        {
            ReadResultTable("Relevant Results", rt);

        }

        /// <summary>
        /// Modified ReadResultTable method to take a table name to 
        /// store in the dataset. This allows multiple tables to 
        /// be saved and exported without collisions.
        /// </summary>
        /// <param name="tablename">Name of the table to save to the DataSet</param>
        /// <param name="rt">Search result table to save</param>
        /// <remarks>This method was modified so that it could be 
        /// reused to store data into a dataset for exporting.</remarks>
        protected void ReadResultTable(string tablename, ResultTable rt)
        {
            //Create new table to hold data
            DataTable relResultsTbl = new DataTable();

            //Name the table the requested name
            relResultsTbl.TableName = tablename;

            //Add the new table to the dataset
            resultDS.Tables.Add(relResultsTbl);

            //Load the table with data from the search
            //result table.
            resultDS.Load(rt, LoadOption.OverwriteChanges, relResultsTbl);

        }

        /// <summary>
        /// High Probility result table storage.
        /// </summary>
        /// <param name="rt">Search result table to store</param>
        /// <remarks>This is a modified ReadResultTable 
        /// method to store it as "High Probility" results.</remarks>
        private void ReadHighResultTable(ResultTable rt)
        {
            ReadResultTable("High Results", rt);
        }

        /// <summary>
        /// Method to search the index for items that match on the 
        /// title (combination of first and last name) and directly on 
        /// First Name and Last Name fields.
        /// </summary>
        /// <param name="strQueryText">Search Terms to look for</param>
        /// <remarks>This method uses the FullTextSQLQuery syntax to do a
        /// wildcard search on fields. It uses a simple split at spaces 
        /// to determine words to search on. The split could be more complex 
        /// by creating a function to search for quotes and so on.</remarks>
        private void titleNameQueryExecute(string strQueryText)
        {
            //Create the FullTextSqlQuery object for searching the index
            FullTextSqlQuery kRequest = new FullTextSqlQuery(ServerContext.Current);

            //Prepare the string to use wildcards
            string strQuery = "'\"" + strQueryText.Replace(" ", "*\" OR \"") + "*\"'";

            //Output the search text to the Trace Listeners
            System.Diagnostics.Trace.TraceInformation("The Search text: " + strQuery);

            //Set the query text to return columns and search appropriate fields.
            //The fields are based on what's indexed in the search settings.
            if (strQueryText.Contains(" "))
            {
                kRequest.QueryText = "select " + this._searchColumns + " FROM Scope() WHERE ("
                + "\"scope\"='People') AND (CONTAINS(title," + strQuery + ") OR (CONTAINS(firstname," + strQuery + ")"
                + "AND CONTAINS(lastname," + strQuery + "))) ORDER BY Rank DESC";
            }
            else
            {
                kRequest.QueryText = "select " + this._searchColumns + " FROM Scope() WHERE ("
                    + "\"scope\"='People') AND (CONTAINS(title," + strQuery + ") OR CONTAINS(firstname," + strQuery + ")"
                    + "OR CONTAINS(lastname," + strQuery + ")) ORDER BY Rank DESC";
            }
            //Write the query to the Trace listeners
            System.Diagnostics.Trace.TraceInformation("The Query text: " + kRequest.QueryText);
            System.Diagnostics.Trace.Flush();

            //Return all result types except best bets
            kRequest.ResultTypes = ResultType.HighConfidenceResults | ResultType.DefinitionResults | ResultType.RelevantResults;

            //Get the result table collection from the query
            ResultTableCollection resultTbls;
            try
            {
                resultTbls = kRequest.Execute();
            }
            catch (Exception ex)
            {
                ///Write error out to trace listener
                System.Diagnostics.Trace.TraceError("Exception: " + Environment.NewLine + ex.ToString());
                resultTbls = null;
                return;
            }
            finally
            {
                ///Flush trace statements out to listeners
                System.Diagnostics.Trace.Flush();
            }

            //Write the type of tables and number of entries returned to
            //the trace listeners
            System.Diagnostics.Trace.TraceInformation("Result Tables Returned");
            foreach (ResultTable resulttbl in resultTbls)
            {
                System.Diagnostics.Trace.TraceInformation("ResultType: " + resulttbl.ResultType.ToString());
                System.Diagnostics.Trace.TraceInformation("Rows Returned: " + resulttbl.RowCount.ToString() + Environment.NewLine);
            }

            //For each table type write the results to the dataset if they are not empty and exists
            if (((int)ResultType.HighConfidenceResults != 0) && (resultTbls.Exists(ResultType.HighConfidenceResults)))
            {
                ResultTable tblResult = resultTbls[ResultType.HighConfidenceResults];
                if (tblResult.TotalRows == 0)
                {
                    lblQueryResult.Text = "No Search Results Returned.";
                }
                else
                {
                    lblQueryResult.Text = string.Empty;
                    ReadResultTable("Name Match High", tblResult);
                }

            }

            if (((int)ResultType.RelevantResults != 0) && (resultTbls.Exists(ResultType.RelevantResults)))
            {
                ResultTable tblResult = resultTbls[ResultType.RelevantResults];
                if (tblResult.TotalRows == 0)
                {
                    lblQueryResult.Text = "No Search Results Returned.";
                }
                else
                {
                    lblQueryResult.Text = string.Empty;
                    ReadResultTable("Name Match Relevant", tblResult);
                }

            }
        }

        /// <summary>
        /// Method to search the index for items that match on 
        /// any field for the query terms.
        /// </summary>
        /// <param name="strQueryText">Search Terms to look for</param>
        /// <remarks>This method uses the FullTextSQLQuery syntax to do a
        /// wildcard search on fields. It uses a simple split at spaces 
        /// to determine words to search on. The split could be more complex 
        /// by creating a function to search for quotes and so on.</remarks>
        private void fullTextQueryExecute(string strQueryText)
        {
            //Create the FullTextSqlQuery object for searching the index
            FullTextSqlQuery kRequest = new FullTextSqlQuery(ServerContext.Current);

            //Prepare the string to use wildcards
            string strQuery = "'\"" + strQueryText.Replace(" ", "*\" AND \"") + "*\"'";


            //Output the search text to the Trace Listeners
            System.Diagnostics.Trace.TraceInformation("The Search text: " + strQuery);

            //Set the query text to return columns and search appropriate fields.
            //The fields are based on what's indexed in the search settings.
            kRequest.QueryText = "select " + this._searchColumns + " FROM Scope() WHERE (\"scope\"='All Sites' "
            + "OR \"scope\"='People') AND CONTAINS(*," + strQuery + ") ORDER BY Rank DESC";

            //Write the query to the Trace listeners
            System.Diagnostics.Trace.TraceInformation("The Query text: " + kRequest.QueryText);
            System.Diagnostics.Trace.Flush();

            //Return all result types except best bets
            kRequest.ResultTypes = ResultType.HighConfidenceResults | ResultType.DefinitionResults | ResultType.RelevantResults;

            //Get the result table collection from the query
            ResultTableCollection resultTbls;
            try
            {
                resultTbls = kRequest.Execute();
            }
            catch (Exception ex)
            {
                ///Write error out to trace listener
                System.Diagnostics.Trace.TraceError("Exception: " + Environment.NewLine + ex.ToString());
                resultTbls = null;
                return;
            }
            finally
            {
                ///Flush trace statements out to listeners
                System.Diagnostics.Trace.Flush();
            }
            //Write the type of tables and number of entries returned to
            //the trace listeners
            System.Diagnostics.Trace.TraceInformation("Result Tables Returned");
            foreach (ResultTable resulttbl in resultTbls)
            {
                System.Diagnostics.Trace.TraceInformation("ResultType: " + resulttbl.ResultType.ToString());
                System.Diagnostics.Trace.TraceInformation("Rows Returned: " + resulttbl.RowCount.ToString() + Environment.NewLine);
            }

            //For each table type write the results to the dataset if they are not empty and exists
            if (((int)ResultType.HighConfidenceResults != 0) && (resultTbls.Exists(ResultType.HighConfidenceResults)))
            {
                ResultTable tblResult = resultTbls[ResultType.HighConfidenceResults];
                if (tblResult.TotalRows == 0)
                {
                    lblQueryResult.Text = "No Search Results Returned.";
                }
                else
                {
                    lblQueryResult.Text = String.Empty;
                    ReadHighResultTable(tblResult);
                }

            }

            if (((int)ResultType.RelevantResults != 0) && (resultTbls.Exists(ResultType.RelevantResults)))
            {
                ResultTable tblResult = resultTbls[ResultType.RelevantResults];
                if (tblResult.TotalRows == 0)
                {
                    lblQueryResult.Text = "No Search Results Returned.";
                }
                else
                {
                    lblQueryResult.Text = string.Empty;
                    ReadResultTable(tblResult);
                }

            }
        }

#if DEBUG

        /// <summary>
        /// Method to take the results in the dataset 
        /// and display a grid with links to each item returned.
        /// </summary>
        /// <param name="grdDs">Dataset containing the resulting search
        /// tables returned</param>
        /// <remarks>This gird is displayed for debugging purposes.
        /// When compiled in release mode, this function is 
        /// not included.</remarks>
        private void fillResultsGrid(DataSet grdDs)
        {
            //Clear out the existing grid list
            grdList.Clear();

            //Go through each table in the dataset
            //and show the restuls of the search.
            foreach (DataTable table in grdDs.Tables)
            {
                //Sort the default view by descending rank
                table.DefaultView.Sort = "Rank Desc";

                //Create a new grid and add it to the list.
                DataGrid grdResults = new DataGrid();
                grdList.Add(grdResults);

                //Set the data source for the grid 
                //to be the default view of the table.
                grdResults.DataSource = table.DefaultView;

                //Set the display properties for the DataGrid.
                grdResults.GridLines = GridLines.None;
                grdResults.CellPadding = 4;
                grdResults.Width = Unit.Percentage(100);
                grdResults.ItemStyle.ForeColor = System.Drawing.Color.Black;
                grdResults.ItemStyle.BackColor = System.Drawing.Color.AliceBlue;
                grdResults.ItemStyle.Font.Size = FontUnit.Smaller;
                grdResults.ItemStyle.Font.Name = "Tahoma";
                grdResults.HeaderStyle.BackColor = System.Drawing.Color.Navy;
                grdResults.HeaderStyle.ForeColor = System.Drawing.Color.White;
                grdResults.HeaderStyle.Font.Bold = true;
                grdResults.HeaderStyle.Font.Name = "Tahoma";
                grdResults.HeaderStyle.Font.Size = FontUnit.Medium;

                //Turn off AutoGenerate for the columns, so that the DataGrid
                //doesn't automatically bind to all of the columns
                //in the search results set.
                //Then create and configure only the columns you want to
                //include in the DataGrid.
                //

                grdResults.AutoGenerateColumns = false;

                //Add the rank column
                BoundColumn colRank = new BoundColumn();
                colRank.DataField = "Rank";
                colRank.HeaderText = "Rank";
                grdResults.Columns.Add(colRank);

                //Add the link to the item found
                HyperLinkColumn colTitle = new HyperLinkColumn();
                colTitle.DataTextField = "Title";
                colTitle.HeaderText = "Title";
                colTitle.DataNavigateUrlField = "Path";
                grdResults.Columns.Add(colTitle);

                //Add the description
                BoundColumn colDescription = new BoundColumn();
                colDescription.DataField = "Description";
                colDescription.HeaderText = "Description";
                grdResults.Columns.Add(colDescription);

                //Add the author
                BoundColumn colAuthor = new BoundColumn();
                colAuthor.DataField = "Author";
                colAuthor.HeaderText = "Author";
                grdResults.Columns.Add(colAuthor);

                //Bind the data to the DataGrid
                grdResults.DataBind();

                //Add the DataGrid to the collection of controls
                Controls.Add(grdResults);

            }

        }
#endif

        /// <summary>
        /// Returns the provider for the consumers to use.
        /// In this case, this webpart is the provider and 
        /// the consumers use this instance to get relevant XML data.
        /// </summary>
        /// <returns>Interface of XMLData (this webpart)</returns>
        /// <remarks>Much of how to do this is covered int the SDK and MSDN</remarks>
        [ASPNET_WebPart.ConnectionProvider("XML Consumer", "IXMLData")]
        public IXMLData ItemProvider()
        {
            return (this);
        }

        /// <summary>
        /// This is the implementation of the interface
        /// to share XML data to other WebParts.
        /// This method converts escaped XML strings for certain fields
        /// to true XML before sending.
        /// </summary>
        public System.Xml.XmlDocument XMLData
        {
            get
            {
                ///Document to return through interface
                System.Xml.XmlDocument returnDoc = new System.Xml.XmlDocument();
                try
                {
                    ///Load the dataset XML information
                    returnDoc.LoadXml(resultDS.GetXml());

                    ///Create a XPath navigator to go through Nodes
                    System.Xml.XPath.XPathNavigator pathnav = returnDoc.CreateNavigator();

                    ///Convert Node to standard XML instead of escaped XML string
                    foreach (System.Xml.XPath.XPathNavigator item in pathnav.Select("/*/*/HITHIGHLIGHTEDSUMMARY"))
                    {
                        item.InnerXml = item.InnerXml.Replace("&lt;", "<").Replace("&gt;", ">");
                        System.Diagnostics.Trace.TraceInformation("The XML item text: " + Environment.NewLine + item.OuterXml);

                    }

                    ///Convert Node to standard XML instead of escaped XML string
                    foreach (System.Xml.XPath.XPathNavigator item in pathnav.Select("/*/*/HITHIGHLIGHTEDPROPERTIES"))
                    {
                        item.InnerXml = item.InnerXml.Replace("&lt;", "<").Replace("&gt;", ">");
                        System.Diagnostics.Trace.TraceInformation("The XML item text: " + Environment.NewLine + item.OuterXml);

                    }

                    ///Convert Node to standard XML instead of escaped XML string
                    foreach (System.Xml.XPath.XPathNavigator item in pathnav.Select("/*/*/RANKDETAIL"))
                    {
                        item.InnerXml = "<![CDATA[" + Environment.NewLine + item.InnerXml.Replace("&lt;", "<").Replace("&gt;", ">") + Environment.NewLine + "]]>";
                        //item.InnerXml = Environment.NewLine + item.InnerXml.Replace("&lt;", "<").Replace("&gt;", ">") + Environment.NewLine;
                        System.Diagnostics.Trace.TraceInformation("The XML item text: " + Environment.NewLine + item.OuterXml);

                    }

                    ///Write resulting XML to trace listener
                    System.Diagnostics.Trace.TraceInformation("The XML text: " + Environment.NewLine + returnDoc.OuterXml);
                }
                catch (Exception ex)
                {
                    ///Write error out to trace listener
                    System.Diagnostics.Trace.TraceError("Exception: " + Environment.NewLine + ex.ToString());
                }
                finally
                {
                    ///Flush trace statements out to listeners
                    System.Diagnostics.Trace.Flush();
                }

                ///return XML to connected parts
                return (returnDoc);
            }

        }

        /// <summary>
        /// Check the Columns to verify the 
        /// default is not needed.
        /// </summary>
        private void checkColumns()
        {
            if (this._searchColumns == string.Empty)
            {
                this._searchColumns = _defaultSearchColumns;

            }

        }

		
    }


}

